using System.Diagnostics;
using System.Text;
using Community.CsharpSqlite.Entity;

namespace Community.CsharpSqlite
{
    public partial class Sqlite3
    {
        /*
    ** 2001 September 15
    **
    ** The author disclaims copyright to this source code.  In place of
    ** a legal notice, here is a blessing:
    **
    **    May you do good and not evil.
    **    May you find forgiveness for yourself and forgive others.
    **    May you share freely, never taking more than you give.
    **
    *************************************************************************
    ** This file contains C code routines that are called by the parser
    ** to handle INSERT statements in SQLite.
    *************************************************************************
    **  Included in SQLite3 port to C#-SQLite;  2008 Noah B Hart
    **  C#-SQLite is an independent reimplementation of the SQLite software library
    **
    **  SQLITE_SOURCE_ID: 2010-03-09 19:31:43 4ae453ea7be69018d8c16eb8dabe05617397dc4d
    **
    **  $Header: Community.CsharpSqlite/src/insert_c.cs,v 6604176a7dbe 2010/03/12 23:35:36 Noah $
    *************************************************************************
    */
        //#include "sqliteInt.h"

        /*
    ** Generate code that will open a table for reading.
    */

        private static void sqlite3OpenTable(
            Parse p, /* Generate code into this VDBE */
            int iCur, /* The cursor number of the table */
            int iDb, /* The database index in sqlite3.aDb[] */
            Table pTab, /* The table to be opened */
            int opcode /* OPCode.OP_OpenRead or OPCode.OP_OpenWrite */
            )
        {
            Vdbe v;
            if (Utility.IsVirtual(pTab)) return;
            v = SelectHelper.GetVdbe(p);
            Debug.Assert(opcode == OPCode.OP_OpenWrite || opcode == OPCode.OP_OpenRead);
            sqlite3TableLock(p, iDb, pTab.tnum, (opcode == OPCode.OP_OpenWrite) ? (byte) 1 : (byte) 0, pTab.zName);
            VdbeAux.VdbeAddOp3(v, opcode, iCur, pTab.tnum, iDb);
            VdbeAux.VdbeChangeP4(v, -1, (pTab.nCol), P4Type.P4_INT32); //SQLITE_INT_TO_PTR( pTab.nCol ), P4Type.P4_INT32 );
            VdbeComment(v, "%s", pTab.zName);
        }

        /*
    ** Return a pointer to the column affinity string associated with index
    ** pIdx. A column affinity string has one character for each column in 
    ** the table, according to the affinity of the column:
    **
    **  Character      Column affinity
    **  ------------------------------
    **  'a'            TEXT
    **  'b'            NONE
    **  'c'            NUMERIC
    **  'd'            INTEGER
    **  'e'            REAL
    **
    ** An extra 'b' is appended to the end of the string to cover the
    ** rowid that appears as the last column in every index.
    **
    ** Memory for the buffer containing the column index affinity string
    ** is managed along with the rest of the Index structure. It will be
    ** released when sqlite3DeleteIndex() is called.
    */

        private static string sqlite3IndexAffinityStr(Vdbe v, Index pIdx)
        {
            if (pIdx.zColAff == null || pIdx.zColAff[0] == '\0')
            {
                /* The first time a column affinity string for a particular index is
        ** required, it is allocated and populated here. It is then stored as
        ** a member of the Index structure for subsequent use.
        **
        ** The column affinity string will eventually be deleted by
        ** sqliteDeleteIndex() when the Index structure itself is cleaned
        ** up.
        */
                int n;
                Table pTab = pIdx.pTable;
                sqlite3 db = VdbeAux.VdbeDb(v);
                var pIdx_zColAff = new StringBuilder(pIdx.nColumn + 2); // (char *)Malloc.sqlite3Malloc(pIdx.nColumn+2);
                //      if ( pIdx_zColAff == null )
                //      {
                //        db.mallocFailed = 1;
                //        return null;
                //      }
                for (n = 0; n < pIdx.nColumn; n++)
                {
                    pIdx_zColAff.Append(pTab.aCol[pIdx.aiColumn[n]].affinity);
                }
                pIdx_zColAff.Append(ColumnAffinityType.SQLITE_AFF_NONE);
                pIdx_zColAff.Append('\0');
                pIdx.zColAff = pIdx_zColAff.ToString();
            }
            return pIdx.zColAff;
        }

        /*
    ** Set P4 of the most recently inserted opcode to a column affinity
    ** string for table pTab. A column affinity string has one character
    ** for each column indexed by the index, according to the affinity of the
    ** column:
    **
    **  Character      Column affinity
    **  ------------------------------
    **  'a'            TEXT
    **  'b'            NONE
    **  'c'            NUMERIC
    **  'd'            INTEGER
    **  'e'            REAL
    */

        private static void sqlite3TableAffinityStr(Vdbe v, Table pTab)
        {
            /* The first time a column affinity string for a particular table
      ** is required, it is allocated and populated here. It is then
      ** stored as a member of the Table structure for subsequent use.
      **
      ** The column affinity string will eventually be deleted by
      ** Build.DeleteTable() when the Table structure itself is cleaned up.
      */
            if (pTab.zColAff == null)
            {
                StringBuilder zColAff;
                int i;
                sqlite3 db = VdbeAux.VdbeDb(v);

                zColAff = new StringBuilder(pTab.nCol + 1); // (char*)Malloc.sqlite3Malloc(db, pTab.nCol + 1);
                if (zColAff == null)
                {
                    ////        db.mallocFailed = 1;
                    return;
                }

                for (i = 0; i < pTab.nCol; i++)
                {
                    zColAff.Append(pTab.aCol[i].affinity);
                }
                //zColAff.Append( '\0' );

                pTab.zColAff = zColAff.ToString();
            }

            VdbeAux.VdbeChangeP4(v, -1, pTab.zColAff, 0);
        }

        /*
    ** Return non-zero if the table pTab in database iDb or any of its indices
    ** have been opened at any point in the VDBE program beginning at location
    ** iStartAddr throught the end of the program.  This is used to see if
    ** a statement of the form  "INSERT INTO <iDb, pTab> SELECT ..." can
    ** run without using temporary table for the results of the SELECT.
    */

        private static bool readsTable(Parse p, int iStartAddr, int iDb, Table pTab)
        {
            Vdbe v = SelectHelper.GetVdbe(p);
            int i;
            int iEnd = VdbeAux.VdbeCurrentAddr(v);
#if !SQLITE_OMIT_VIRTUALTABLE
  VTable pVTab = Utility.IsVirtual(pTab) ? sqlite3GetVTable(p,db, pTab) : null;
#endif

            for (i = iStartAddr; i < iEnd; i++)
            {
                VdbeOp pOp = sqlite3VdbeGetOp(v, i);
                Debug.Assert(pOp != null);
                if (pOp.opcode == OPCode.OP_OpenRead && pOp.p3 == iDb)
                {
                    Index pIndex;
                    int tnum = pOp.p2;
                    if (tnum == pTab.tnum)
                    {
                        return true;
                    }
                    for (pIndex = pTab.pIndex; pIndex != null; pIndex = pIndex.pNext)
                    {
                        if (tnum == pIndex.tnum)
                        {
                            return true;
                        }
                    }
                }
#if !SQLITE_OMIT_VIRTUALTABLE
if( pOp.opcode==OPCode.OP_VOpen && pOp.p4.pVtab==pVTab){
Debug.Assert( pOp.p4.pVtab!=0 );
Debug.Assert( pOp.p4type==P4Type.P4_VTAB );
return true;
}
#endif
            }
            return false;
        }

#if !SQLITE_OMIT_AUTOINCREMENT
        /*
** Locate or create an AutoincInfo structure associated with table pTab
** which is in database iDb.  Return the register number for the register
** that holds the maximum rowid.
**
** There is at most one AutoincInfo structure per table even if the
** same table is autoincremented multiple times due to inserts within
** triggers.  A new AutoincInfo structure is created if this is the
** first use of table pTab.  On 2nd and subsequent uses, the original
** AutoincInfo structure is used.
**
** Three memory locations are allocated:
**
**   (1)  Register to hold the name of the pTab table.
**   (2)  Register to hold the maximum ROWID of pTab.
**   (3)  Register to hold the rowid in sqlite_sequence of pTab
**
** The 2nd register is the one that is returned.  That is all the
** insert routine needs to know about.
*/

        private static int autoIncBegin(
            Parse pParse, /* Parsing context */
            int iDb, /* Index of the database holding pTab */
            Table pTab /* The table we are writing to */
            )
        {
            int memId = 0; /* Register holding maximum rowid */
            if ((pTab.tabFlags & TabFlag.TF_Autoincrement) != 0)
            {
                Parse pToplevel = sqlite3ParseToplevel(pParse);
                AutoincInfo pInfo;

                pInfo = pToplevel.pAinc;
                while (pInfo != null && pInfo.pTab != pTab)
                {
                    pInfo = pInfo.pNext;
                }
                if (pInfo == null)
                {
                    pInfo = new AutoincInfo(); //sqlite3DbMallocRaw(pParse.db, sizeof(*pInfo));
                    //if( pInfo==0 ) return 0;
                    pInfo.pNext = pToplevel.pAinc;
                    pToplevel.pAinc = pInfo;
                    pInfo.pTab = pTab;
                    pInfo.iDb = iDb;
                    pToplevel.nMem++; /* Register to hold name of table */
                    pInfo.regCtr = ++pToplevel.nMem; /* Max rowid register */
                    pToplevel.nMem++; /* Rowid in sqlite_sequence */
                }
                memId = pInfo.regCtr;
            }
            return memId;
        }

        /*
    ** This routine generates code that will initialize all of the
    ** register used by the autoincrement tracker.
    */

        private static void sqlite3AutoincrementBegin(Parse pParse)
        {
            AutoincInfo p; /* Information about an AUTOINCREMENT */
            sqlite3 db = pParse.db; /* The database connection */
            Db pDb; /* Database only autoinc table */
            int memId; /* Register holding max rowid */
            int addr; /* A VDBE address */
            Vdbe v = pParse.pVdbe; /* VDBE under construction */

            /* This routine is never called during trigger-generation.  It is
      ** only called from the top-level */
            Debug.Assert(pParse.pTriggerTab == null);
            Debug.Assert(pParse == sqlite3ParseToplevel(pParse));

            Debug.Assert(v != null); /* We failed long ago if this is not so */
            for (p = pParse.pAinc; p != null; p = p.pNext)
            {
                pDb = db.aDb[p.iDb];
                memId = p.regCtr;
                sqlite3OpenTable(pParse, 0, p.iDb, pDb.pSchema.pSeqTab, OPCode.OP_OpenRead);
                addr = VdbeAux.VdbeCurrentAddr(v);
                VdbeAux.VdbeAddOp4(v, OPCode.OP_String8, 0, memId - 1, 0, p.pTab.zName, 0);
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Rewind, 0, addr + 9);
                VdbeAux.VdbeAddOp3(v, OPCode.OP_Column, 0, 0, memId);
                VdbeAux.VdbeAddOp3(v, OPCode.OP_Ne, memId - 1, addr + 7, memId);
                VdbeAux.VdbeChangeP5(v, Const.SQLITE_JUMPIFNULL);
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Rowid, 0, memId + 1);
                VdbeAux.VdbeAddOp3(v, OPCode.OP_Column, 0, 1, memId);
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Goto, 0, addr + 9);
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Next, 0, addr + 2);
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Integer, 0, memId);
                VdbeAux.VdbeAddOp0(v, OPCode.OP_Close);
            }
        }

        /*
    ** Update the maximum rowid for an autoincrement calculation.
    **
    ** This routine should be called when the top of the stack holds a
    ** new rowid that is about to be inserted.  If that new rowid is
    ** larger than the maximum rowid in the memId memory cell, then the
    ** memory cell is updated.  The stack is unchanged.
    */

        private static void autoIncStep(Parse pParse, int memId, int regRowid)
        {
            if (memId > 0)
            {
                VdbeAux.VdbeAddOp2(pParse.pVdbe, OPCode.OP_MemMax, memId, regRowid);
            }
        }

        /*
    ** This routine generates the code needed to write autoincrement
    ** maximum rowid values back into the sqlite_sequence register.
    ** Every statement that might do an INSERT into an autoincrement
    ** table (either directly or through triggers) needs to call this
    ** routine just before the "exit" code.
    */

        private static void sqlite3AutoincrementEnd(Parse pParse)
        {
            AutoincInfo p;
            Vdbe v = pParse.pVdbe;
            sqlite3 db = pParse.db;

            Debug.Assert(v != null);
            for (p = pParse.pAinc; p != null; p = p.pNext)
            {
                Db pDb = db.aDb[p.iDb];
                int j1, j2, j3, j4, j5;
                int iRec;
                int memId = p.regCtr;

                iRec = ExprHelper.GetTempReg(pParse);
                sqlite3OpenTable(pParse, 0, p.iDb, pDb.pSchema.pSeqTab, OPCode.OP_OpenWrite);
                j1 = VdbeAux.VdbeAddOp1(v, OPCode.OP_NotNull, memId + 1);
                j2 = VdbeAux.VdbeAddOp0(v, OPCode.OP_Rewind);
                j3 = VdbeAux.VdbeAddOp3(v, OPCode.OP_Column, 0, 0, iRec);
                j4 = VdbeAux.VdbeAddOp3(v, OPCode.OP_Eq, memId - 1, 0, iRec);
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Next, 0, j3);
                VdbeAux.VdbeJumpHere(v, j2);
                VdbeAux.VdbeAddOp2(v, OPCode.OP_NewRowid, 0, memId + 1);
                j5 = VdbeAux.VdbeAddOp0(v, OPCode.OP_Goto);
                VdbeAux.VdbeJumpHere(v, j4);
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Rowid, 0, memId + 1);
                VdbeAux.VdbeJumpHere(v, j1);
                VdbeAux.VdbeJumpHere(v, j5);
                VdbeAux.VdbeAddOp3(v, OPCode.OP_MakeRecord, memId - 1, 2, iRec);
                VdbeAux.VdbeAddOp3(v, OPCode.OP_Insert, 0, iRec, memId + 1);
                VdbeAux.VdbeChangeP5(v, P5Value.OPFLAG_APPEND);
                VdbeAux.VdbeAddOp0(v, OPCode.OP_Close);
                ExprHelper.ReleaseTempReg(pParse, iRec);
            }
        }
#else
/*
** If SQLITE_OMIT_AUTOINCREMENT is defined, then the three routines
** above are all no-ops
*/
//# define autoIncBegin(A,B,C) (0)
//# define autoIncStep(A,B,C)
#endif
        // * SQLITE_OMIT_AUTOINCREMENT */


        /* Forward declaration */
        //static int xferOptimization(
        //  Parse pParse,        /* Parser context */
        //  Table pDest,         /* The table we are inserting into */
        //  Select pSelect,      /* A SELECT statement to use as the data source */
        //  int onError,          /* How to handle constraint errors */
        //  int iDbDest           /* The database of pDest */
        //);

        /*
    ** This routine is call to handle SQL of the following forms:
    **
    **    insert into TABLE (IDLIST) values(EXPRLIST)
    **    insert into TABLE (IDLIST) select
    **
    ** The IDLIST following the table name is always optional.  If omitted,
    ** then a list of all columns for the table is substituted.  The IDLIST
    ** appears in the pColumn parameter.  pColumn is NULL if IDLIST is omitted.
    **
    ** The pList parameter holds EXPRLIST in the first form of the INSERT
    ** statement above, and pSelect is NULL.  For the second form, pList is
    ** NULL and pSelect is a pointer to the select statement used to generate
    ** data for the insert.
    **
    ** The code generated follows one of four templates.  For a simple
    ** select with data coming from a VALUES clause, the code executes
    ** once straight down through.  Pseudo-code follows (we call this
    ** the "1st template"):
    **
    **         open write cursor to <table> and its indices
    **         puts VALUES clause expressions onto the stack
    **         write the resulting record into <table>
    **         cleanup
    **
    ** The three remaining templates assume the statement is of the form
    **
    **   INSERT INTO <table> SELECT ...
    **
    ** If the SELECT clause is of the restricted form "SELECT * FROM <table2>" -
    ** in other words if the SELECT pulls all columns from a single table
    ** and there is no WHERE or LIMIT or GROUP BY or ORDER BY clauses, and
    ** if <table2> and <table1> are distinct tables but have identical
    ** schemas, including all the same indices, then a special optimization
    ** is invoked that copies raw records from <table2> over to <table1>.
    ** See the xferOptimization() function for the implementation of this
    ** template.  This is the 2nd template.
    **
    **         open a write cursor to <table>
    **         open read cursor on <table2>
    **         transfer all records in <table2> over to <table>
    **         close cursors
    **         foreach index on <table>
    **           open a write cursor on the <table> index
    **           open a read cursor on the corresponding <table2> index
    **           transfer all records from the read to the write cursors
    **           close cursors
    **         end foreach
    **
    ** The 3rd template is for when the second template does not apply
    ** and the SELECT clause does not read from <table> at any time.
    ** The generated code follows this template:
    **
    **         EOF <- 0
    **         X <- A
    **         goto B
    **      A: setup for the SELECT
    **         loop over the rows in the SELECT
    **           load values into registers R..R+n
    **           yield X
    **         end loop
    **         cleanup after the SELECT
    **         EOF <- 1
    **         yield X
    **         goto A
    **      B: open write cursor to <table> and its indices
    **      C: yield X
    **         if EOF goto D
    **         insert the select result into <table> from R..R+n
    **         goto C
    **      D: cleanup
    **
    ** The 4th template is used if the insert statement takes its
    ** values from a SELECT but the data is being inserted into a table
    ** that is also read as part of the SELECT.  In the third form,
    ** we have to use a intermediate table to store the results of
    ** the select.  The template is like this:
    **
    **         EOF <- 0
    **         X <- A
    **         goto B
    **      A: setup for the SELECT
    **         loop over the tables in the SELECT
    **           load value into register R..R+n
    **           yield X
    **         end loop
    **         cleanup after the SELECT
    **         EOF <- 1
    **         yield X
    **         halt-error
    **      B: open temp table
    **      L: yield X
    **         if EOF goto M
    **         insert row from R..R+n into temp table
    **         goto L
    **      M: open write cursor to <table> and its indices
    **         rewind temp table
    **      C: loop over rows of intermediate table
    **           transfer values form intermediate table into <table>
    **         end loop
    **      D: cleanup
    */
        // OVERLOADS, so I don't need to rewrite parse.c
        private static void sqlite3Insert(Parse pParse, SrcList pTabList, int null_3, int null_4, IdList pColumn,
                                          int onError)
        {
            sqlite3Insert(pParse, pTabList, null, null, pColumn, onError);
        }

        private static void sqlite3Insert(Parse pParse, SrcList pTabList, int null_3, Select pSelect, IdList pColumn,
                                          int onError)
        {
            sqlite3Insert(pParse, pTabList, null, pSelect, pColumn, onError);
        }

        private static void sqlite3Insert(Parse pParse, SrcList pTabList, ExprList pList, int null_4, IdList pColumn,
                                          int onError)
        {
            sqlite3Insert(pParse, pTabList, pList, null, pColumn, onError);
        }

        private static void sqlite3Insert(
            Parse pParse, /* Parser context */
            SrcList pTabList, /* Name of table into which we are inserting */
            ExprList pList, /* List of values to be inserted */
            Select pSelect, /* A SELECT statement to use as the data source */
            IdList pColumn, /* Column names corresponding to IDLIST. */
            int onError /* How to handle constraint errors */
            )
        {
            sqlite3 db; /* The main database structure */
            Table pTab; /* The table to insert into.  aka TABLE */
            string zTab; /* Name of the table into which we are inserting */
            string zDb; /* Name of the database holding this table */
            int i = 0;
            int j = 0;
            int idx = 0; /* Loop counters */
            Vdbe v; /* Generate code into this virtual machine */
            Index pIdx; /* For looping over indices of the table */
            int nColumn; /* Number of columns in the data */
            int nHidden = 0; /* Number of hidden columns if TABLE is virtual */
            int baseCur = 0; /* VDBE VdbeCursor number for pTab */
            int keyColumn = -1; /* Column that is the INTEGER PRIMARY KEY */
            int endOfLoop = 0; /* Label for the end of the insertion loop */
            bool useTempTable = false; /* Store SELECT results in intermediate table */
            int srcTab = 0; /* Data comes from this temporary cursor if >=0 */
            int addrInsTop = 0; /* Jump to label "D" */
            int addrCont = 0; /* Top of insert loop. Label "C" in templates 3 and 4 */
            int addrSelect = 0; /* Address of coroutine that implements the SELECT */
            SelectDest dest; /* Destination for SELECT on rhs of INSERT */
            int iDb; /* Index of database holding TABLE */
            Db pDb; /* The database containing table being inserted into */
            bool appendFlag = false; /* True if the insert is Utility.Likely to be an append */

            /* Register allocations */
            int regFromSelect = 0; /* Base register for data coming from SELECT */
            int regAutoinc = 0; /* Register holding the AUTOINCREMENT counter */
            int regRowCount = 0; /* Memory cell used for the row counter */
            int regIns; /* Block of regs holding rowid+data being inserted */
            int regRowid; /* registers holding insert rowid */
            int regData; /* register holding first column to insert */
            int regRecord; /* Holds the assemblied row record */
            int regEof = 0; /* Register recording end of SELECT data */
            int[] aRegIdx = null; /* One register allocated to each index */


#if !SQLITE_OMIT_TRIGGER
            bool isView = false; /* True if attempting to insert into a view */
            Trigger pTrigger; /* List of triggers on pTab, if required */
            int tmask = 0; /* Mask of trigger times */
#endif

            db = pParse.db;
            dest = new SelectDest(); // memset( &dest, 0, sizeof( dest ) );

            if (pParse.nErr != 0 /*|| db.mallocFailed != 0 */)
            {
                goto insert_cleanup;
            }

            /* Locate the table into which we will be inserting new information.
      */
            Debug.Assert(pTabList.nSrc == 1);
            zTab = pTabList.a[0].zName;
            if (UnitTest.NEVER(zTab == null)) goto insert_cleanup;
            pTab = sqlite3SrcListLookup(pParse, pTabList);
            if (pTab == null)
            {
                goto insert_cleanup;
            }
            iDb = sqlite3SchemaToIndex(db, pTab.pSchema);
            Debug.Assert(iDb < db.nDb);
            pDb = db.aDb[iDb];
            zDb = pDb.zName;
#if !SQLITE_OMIT_AUTHORIZATION
if( sqlite3AuthCheck(pParse, ActionCode.SQLITE_INSERT, pTab.zName, 0, zDb) ){
goto insert_cleanup;
}
#endif
            /* Figure out if we have any triggers and if the table being
** inserted into is a view
*/
#if !SQLITE_OMIT_TRIGGER
            pTrigger = sqlite3TriggersExist(pParse, pTab, TokenKeyword.TK_INSERT, null, ref tmask);
            isView = pTab.pSelect != null;
#else
      Trigger pTrigger = null;  //# define pTrigger 0
      int tmask = 0;            //# define tmask 0
      bool isView = false;
#endif
#if  SQLITE_OMIT_VIEW
//# undef isView
isView = false;
#endif
#if !SQLITE_OMIT_TRIGGER
            Debug.Assert((pTrigger != null && tmask != 0) || (pTrigger == null && tmask == 0));
#endif

#if !SQLITE_OMIT_VIEW
            /* If pTab is really a view, make sure it has been initialized.
      ** ViewGetColumnNames() is a no-op if pTab is not a view (or virtual
      ** module table).
      */
            if (Build.ViewGetColumnNames(pParse, pTab) != -0)
            {
                goto insert_cleanup;
            }
#endif

            /* Ensure that:
      *  (a) the table is not read-only, 
      *  (b) that if it is a view then ON INSERT triggers exist
      */
            if (sqlite3IsReadOnly(pParse, pTab, tmask))
            {
                goto insert_cleanup;
            }

            /* Allocate a VDBE
      */
            v = SelectHelper.GetVdbe(pParse);
            if (v == null) goto insert_cleanup;
            if (pParse.nested == 0) VdbeAux.VdbeCountChanges(v);
            Build.BeginWriteOperation(pParse, (pSelect != null || pTrigger != null) ? 1 : 0, iDb);

#if !SQLITE_OMIT_XFER_OPT
            /* If the statement is of the form
**
**       INSERT INTO <table1> SELECT * FROM <table2>;
**
** Then special optimizations can be applied that make the transfer
** very fast and which reduce fragmentation of indices.
**
** This is the 2nd template.
*/
            if (pColumn == null && xferOptimization(pParse, pTab, pSelect, onError, iDb) != 0)
            {
                Debug.Assert(null == pTrigger);
                Debug.Assert(pList == null);
                goto insert_end;
            }
#endif
            // * SQLITE_OMIT_XFER_OPT */

            /* If this is an AUTOINCREMENT table, look up the sequence number in the
** sqlite_sequence table and store it in memory cell regAutoinc.
*/
            regAutoinc = autoIncBegin(pParse, iDb, pTab);

            /* Figure out how many columns of data are supplied.  If the data
      ** is coming from a SELECT statement, then generate a co-routine that
      ** produces a single row of the SELECT on each invocation.  The
      ** co-routine is the common header to the 3rd and 4th templates.
      */
            if (pSelect != null)
            {
                /* Data is coming from a SELECT.  Generate code to implement that SELECT
        ** as a co-routine.  The code is common to both the 3rd and 4th
        ** templates:
        **
        **         EOF <- 0
        **         X <- A
        **         goto B
        **      A: setup for the SELECT
        **         loop over the tables in the SELECT
        **           load value into register R..R+n
        **           yield X
        **         end loop
        **         cleanup after the SELECT
        **         EOF <- 1
        **         yield X
        **         halt-error
        **
        ** On each invocation of the co-routine, it puts a single row of the
        ** SELECT result into registers dest.iMem...dest.iMem+dest.nMem-1.
        ** (These output registers are allocated by sqlite3Select().)  When
        ** the SELECT completes, it sets the EOF flag stored in regEof.
        */
                int rc = 0, j1;

                regEof = ++pParse.nMem;
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Integer, 0, regEof); /* EOF <- 0 */
#if SQLITE_DEBUG
                VdbeComment(v, "SELECT eof flag");
#endif
                sqlite3SelectDestInit(dest, SelectResultType.SRT_Coroutine, ++pParse.nMem);
                addrSelect = VdbeAux.VdbeCurrentAddr(v) + 2;
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Integer, addrSelect - 1, dest.iParm);
                j1 = VdbeAux.VdbeAddOp2(v, OPCode.OP_Goto, 0, 0);
#if SQLITE_DEBUG
                VdbeComment(v, "Jump over SELECT coroutine");
#endif
                /* Resolve the expressions in the SELECT statement and execute it. */
                rc = sqlite3Select(pParse, pSelect, ref dest);
                Debug.Assert(pParse.nErr == 0 || rc != 0);
                if (rc != 0 || UnitTest.NEVER(pParse.nErr != 0) /*|| db.mallocFailed != 0 */)
                {
                    goto insert_cleanup;
                }
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Integer, 1, regEof); /* EOF <- 1 */
                VdbeAux.VdbeAddOp1(v, OPCode.OP_Yield, dest.iParm); /* yield X */
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Halt, StatusCode.SQLITE_INTERNAL, OnConstraintError.OE_Abort);
#if SQLITE_DEBUG
                VdbeComment(v, "End of SELECT coroutine");
#endif
                VdbeAux.VdbeJumpHere(v, j1); /* label B: */

                regFromSelect = dest.iMem;
                Debug.Assert(pSelect.pEList != null);
                nColumn = pSelect.pEList.nExpr;
                Debug.Assert(dest.nMem == nColumn);

                /* Set useTempTable to TRUE if the result of the SELECT statement
        ** should be written into a temporary table (template 4).  Set to
        ** FALSE if each* row of the SELECT can be written directly into
        ** the destination table (template 3).
        **
        ** A temp table must be used if the table being updated is also one
        ** of the tables being read by the SELECT statement.  Also use a
        ** temp table in the case of row triggers.
        */
                if (pTrigger != null || readsTable(pParse, addrSelect, iDb, pTab))
                {
                    useTempTable = true;
                }

                if (useTempTable)
                {
                    /* Invoke the coroutine to extract information from the SELECT
          ** and add it to a transient table srcTab.  The code generated
          ** here is from the 4th template:
          **
          **      B: open temp table
          **      L: yield X
          **         if EOF goto M
          **         insert row from R..R+n into temp table
          **         goto L
          **      M: ...
          */
                    int regRec; /* Register to hold packed record */
                    int regTempRowid; /* Register to hold temp table ROWID */
                    int addrTop; /* Label "L" */
                    int addrIf; /* Address of jump to M */

                    srcTab = pParse.nTab++;
                    regRec = ExprHelper.GetTempReg(pParse);
                    regTempRowid = ExprHelper.GetTempReg(pParse);
                    VdbeAux.VdbeAddOp2(v, OPCode.OP_OpenEphemeral, srcTab, nColumn);
                    addrTop = VdbeAux.VdbeAddOp1(v, OPCode.OP_Yield, dest.iParm);
                    addrIf = VdbeAux.VdbeAddOp1(v, OPCode.OP_If, regEof);
                    VdbeAux.VdbeAddOp3(v, OPCode.OP_MakeRecord, regFromSelect, nColumn, regRec);
                    VdbeAux.VdbeAddOp2(v, OPCode.OP_NewRowid, srcTab, regTempRowid);
                    VdbeAux.VdbeAddOp3(v, OPCode.OP_Insert, srcTab, regRec, regTempRowid);
                    VdbeAux.VdbeAddOp2(v, OPCode.OP_Goto, 0, addrTop);
                    VdbeAux.VdbeJumpHere(v, addrIf);
                    ExprHelper.ReleaseTempReg(pParse, regRec);
                    ExprHelper.ReleaseTempReg(pParse, regTempRowid);
                }
            }
            else
            {
                /* This is the case if the data for the INSERT is coming from a VALUES
        ** clause
        */
                NameContext sNC;
                sNC = new NameContext(); // memset( &sNC, 0, sNC ).Length;
                sNC.pParse = pParse;
                srcTab = -1;
                Debug.Assert(!useTempTable);
                nColumn = pList != null ? pList.nExpr : 0;
                for (i = 0; i < nColumn; i++)
                {
                    if (sqlite3ResolveExprNames(sNC, ref pList.a[i].pExpr) != 0)
                    {
                        goto insert_cleanup;
                    }
                }
            }

            /* Make sure the number of columns in the source data matches the number
      ** of columns to be inserted into the table.
      */
            if (Utility.IsVirtual(pTab))
            {
                for (i = 0; i < pTab.nCol; i++)
                {
                    nHidden += (Utility.IsHiddenColumn(pTab.aCol[i]) ? 1 : 0);
                }
            }
            if (pColumn == null && nColumn != 0 && nColumn != (pTab.nCol - nHidden))
            {
                Utility.Sqlite3ErrorMsg(pParse,
                                "table %S has %d columns but %d values were supplied",
                                pTabList, 0, pTab.nCol - nHidden, nColumn);
                goto insert_cleanup;
            }
            if (pColumn != null && nColumn != pColumn.nId)
            {
                Utility.Sqlite3ErrorMsg(pParse, "%d values for %d columns", nColumn, pColumn.nId);
                goto insert_cleanup;
            }

            /* If the INSERT statement included an IDLIST term, then make sure
      ** all elements of the IDLIST really are columns of the table and
      ** remember the column indices.
      **
      ** If the table has an INTEGER PRIMARY KEY column and that column
      ** is named in the IDLIST, then record in the keyColumn variable
      ** the index into IDLIST of the primary key column.  keyColumn is
      ** the index of the primary key as it appears in IDLIST, not as
      ** is appears in the original table.  (The index of the primary
      ** key in the original table is pTab.iPKey.)
      */
            if (pColumn != null)
            {
                for (i = 0; i < pColumn.nId; i++)
                {
                    pColumn.a[i].idx = -1;
                }
                for (i = 0; i < pColumn.nId; i++)
                {
                    for (j = 0; j < pTab.nCol; j++)
                    {
                        if (Utility.Sqlite3StrICmp(pColumn.a[i].zName, pTab.aCol[j].zName) == 0)
                        {
                            pColumn.a[i].idx = j;
                            if (j == pTab.iPKey)
                            {
                                keyColumn = i;
                            }
                            break;
                        }
                    }
                    if (j >= pTab.nCol)
                    {
                        if (ExprHelper.IsRowid(pColumn.a[i].zName))
                        {
                            keyColumn = i;
                        }
                        else
                        {
                            Utility.Sqlite3ErrorMsg(pParse, "table %S has no column named %s",
                                            pTabList, 0, pColumn.a[i].zName);
                            pParse.nErr++;
                            goto insert_cleanup;
                        }
                    }
                }
            }

            /* If there is no IDLIST term but the table has an integer primary
      ** key, the set the keyColumn variable to the primary key column index
      ** in the original table definition.
      */
            if (pColumn == null && nColumn > 0)
            {
                keyColumn = pTab.iPKey;
            }

            /* Initialize the count of rows to be inserted
      */
            if ((db.flags & Flag.SQLITE_CountRows) != 0)
            {
                regRowCount = ++pParse.nMem;
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Integer, 0, regRowCount);
            }

            /* If this is not a view, open the table and and all indices */
            if (!isView)
            {
                int nIdx;

                baseCur = pParse.nTab;
                nIdx = sqlite3OpenTableAndIndices(pParse, pTab, baseCur, OPCode.OP_OpenWrite);
                aRegIdx = new int[nIdx + 1]; // sqlite3DbMallocRaw( db, sizeof( int ) * ( nIdx + 1 ) );
                if (aRegIdx == null)
                {
                    goto insert_cleanup;
                }
                for (i = 0; i < nIdx; i++)
                {
                    aRegIdx[i] = ++pParse.nMem;
                }
            }

            /* This is the top of the main insertion loop */
            if (useTempTable)
            {
                /* This block codes the top of loop only.  The complete loop is the
        ** following pseudocode (template 4):
        **
        **         rewind temp table
        **      C: loop over rows of intermediate table
        **           transfer values form intermediate table into <table>
        **         end loop
        **      D: ...
        */
                addrInsTop = VdbeAux.VdbeAddOp1(v, OPCode.OP_Rewind, srcTab);
                addrCont = VdbeAux.VdbeCurrentAddr(v);
            }
            else if (pSelect != null)
            {
                /* This block codes the top of loop only.  The complete loop is the
        ** following pseudocode (template 3):
        **
        **      C: yield X
        **         if EOF goto D
        **         insert the select result into <table> from R..R+n
        **         goto C
        **      D: ...
        */
                addrCont = VdbeAux.VdbeAddOp1(v, OPCode.OP_Yield, dest.iParm);
                addrInsTop = VdbeAux.VdbeAddOp1(v, OPCode.OP_If, regEof);
            }

            /* Allocate registers for holding the rowid of the new row,
      ** the content of the new row, and the assemblied row record.
      */
            regRecord = ++pParse.nMem;
            regRowid = regIns = pParse.nMem + 1;
            pParse.nMem += pTab.nCol + 1;
            if (Utility.IsVirtual(pTab))
            {
                regRowid++;
                pParse.nMem++;
            }
            regData = regRowid + 1;

            /* Run the BEFORE and INSTEAD OF triggers, if there are any
      */
            endOfLoop = VdbeAux.VdbeMakeLabel(v);
#if !SQLITE_OMIT_TRIGGER
            if ((tmask & TRIGGER_BEFORE) != 0)
            {
                int regCols = ExprHelper.GetTempRange(pParse, pTab.nCol + 1);

                /* build the NEW.* reference row.  Note that if there is an INTEGER
        ** PRIMARY KEY into which a NULL is being inserted, that NULL will be
        ** translated into a unique ID for the row.  But on a BEFORE trigger,
        ** we do not know what the unique ID will be (because the insert has
        ** not happened yet) so we substitute a rowid of -1
        */
                if (keyColumn < 0)
                {
                    VdbeAux.VdbeAddOp2(v, OPCode.OP_Integer, -1, regCols);
                }
                else
                {
                    int j1;
                    if (useTempTable)
                    {
                        VdbeAux.VdbeAddOp3(v, OPCode.OP_Column, srcTab, keyColumn, regCols);
                    }
                    else
                    {
                        Debug.Assert(pSelect == null); /* Otherwise useTempTable is true */
                        ExprHelper.ExprCode(pParse, pList.a[keyColumn].pExpr, regCols);
                    }
                    j1 = VdbeAux.VdbeAddOp1(v, OPCode.OP_NotNull, regCols);
                    VdbeAux.VdbeAddOp2(v, OPCode.OP_Integer, -1, regCols);
                    VdbeAux.VdbeJumpHere(v, j1);
                    VdbeAux.VdbeAddOp1(v, OPCode.OP_MustBeInt, regCols);
                }
                /* Cannot have triggers on a virtual table. If it were possible,
        ** this block would have to account for hidden column.
        */
                Debug.Assert(!Utility.IsVirtual(pTab));
                /* Create the new column data
        */
                for (i = 0; i < pTab.nCol; i++)
                {
                    if (pColumn == null)
                    {
                        j = i;
                    }
                    else
                    {
                        for (j = 0; j < pColumn.nId; j++)
                        {
                            if (pColumn.a[j].idx == i) break;
                        }
                    }
                    if (pColumn != null && j >= pColumn.nId)
                    {
                        ExprHelper.ExprCode(pParse, pTab.aCol[i].pDflt, regCols + i + 1);
                    }
                    else if (useTempTable)
                    {
                        VdbeAux.VdbeAddOp3(v, OPCode.OP_Column, srcTab, j, regCols + i + 1);
                    }
                    else
                    {
                        Debug.Assert(pSelect == null); /* Otherwise useTempTable is true */
                        ExprHelper.ExprCodeAndCache(pParse, pList.a[j].pExpr, regCols + i + 1);
                    }
                }

                /* If this is an INSERT on a view with an INSTEAD OF INSERT trigger,
        ** do not attempt any conversions before assembling the record.
        ** If this is a real table, attempt conversions as required by the
        ** table column affinities.
        */
                if (!isView)
                {
                    VdbeAux.VdbeAddOp2(v, OPCode.OP_Affinity, regCols + 1, pTab.nCol);
                    sqlite3TableAffinityStr(v, pTab);
                }

                /* Fire BEFORE or INSTEAD OF triggers */
                sqlite3CodeRowTrigger(pParse, pTrigger, TokenKeyword.TK_INSERT, null, TRIGGER_BEFORE,
                                      pTab, regCols - pTab.nCol - 1, onError, endOfLoop);

                ExprHelper.ReleaseTempRange(pParse, regCols, pTab.nCol + 1);
            }
#endif

            /* Push the record number for the new entry onto the stack.  The
** record number is a randomly generate integer created by NewRowid
** except when the table has an INTEGER PRIMARY KEY column, in which
** case the record number is the same as that column.
*/
            if (!isView)
            {
                if (Utility.IsVirtual(pTab))
                {
                    /* The row that the VUpdate opcode will delete: none */
                    VdbeAux.VdbeAddOp2(v, OPCode.OP_Null, 0, regIns);
                }
                if (keyColumn >= 0)
                {
                    if (useTempTable)
                    {
                        VdbeAux.VdbeAddOp3(v, OPCode.OP_Column, srcTab, keyColumn, regRowid);
                    }
                    else if (pSelect != null)
                    {
                        VdbeAux.VdbeAddOp2(v, OPCode.OP_SCopy, regFromSelect + keyColumn, regRowid);
                    }
                    else
                    {
                        VdbeOp pOp;
                        ExprHelper.ExprCode(pParse, pList.a[keyColumn].pExpr, regRowid);
                        pOp = sqlite3VdbeGetOp(v, -1);
                        if (UnitTest.ALWAYS(pOp != null) && pOp.opcode == OPCode.OP_Null && !Utility.IsVirtual(pTab))
                        {
                            appendFlag = true;
                            pOp.opcode = OPCode.OP_NewRowid;
                            pOp.p1 = baseCur;
                            pOp.p2 = regRowid;
                            pOp.p3 = regAutoinc;
                        }
                    }
                    /* If the PRIMARY KEY expression is NULL, then use OPCode.OP_NewRowid
          ** to generate a unique primary key value.
          */
                    if (!appendFlag)
                    {
                        int j1;
                        if (!Utility.IsVirtual(pTab))
                        {
                            j1 = VdbeAux.VdbeAddOp1(v, OPCode.OP_NotNull, regRowid);
                            VdbeAux.VdbeAddOp3(v, OPCode.OP_NewRowid, baseCur, regRowid, regAutoinc);
                            VdbeAux.VdbeJumpHere(v, j1);
                        }
                        else
                        {
                            j1 = VdbeAux.VdbeCurrentAddr(v);
                            VdbeAux.VdbeAddOp2(v, OPCode.OP_IsNull, regRowid, j1 + 2);
                        }
                        VdbeAux.VdbeAddOp1(v, OPCode.OP_MustBeInt, regRowid);
                    }
                }
                else if (Utility.IsVirtual(pTab))
                {
                    VdbeAux.VdbeAddOp2(v, OPCode.OP_Null, 0, regRowid);
                }
                else
                {
                    VdbeAux.VdbeAddOp3(v, OPCode.OP_NewRowid, baseCur, regRowid, regAutoinc);
                    appendFlag = true;
                }
                autoIncStep(pParse, regAutoinc, regRowid);

                /* Push onto the stack, data for all columns of the new entry, beginning
        ** with the first column.
        */
                nHidden = 0;
                for (i = 0; i < pTab.nCol; i++)
                {
                    int iRegStore = regRowid + 1 + i;
                    if (i == pTab.iPKey)
                    {
                        /* The value of the INTEGER PRIMARY KEY column is always a NULL.
            ** Whenever this column is read, the record number will be substituted
            ** in its place.  So will fill this column with a NULL to avoid
            ** taking up data space with information that will never be used. */
                        VdbeAux.VdbeAddOp2(v, OPCode.OP_Null, 0, iRegStore);
                        continue;
                    }
                    if (pColumn == null)
                    {
                        if (Utility.IsHiddenColumn(pTab.aCol[i]))
                        {
                            Debug.Assert(Utility.IsVirtual(pTab));
                            j = -1;
                            nHidden++;
                        }
                        else
                        {
                            j = i - nHidden;
                        }
                    }
                    else
                    {
                        for (j = 0; j < pColumn.nId; j++)
                        {
                            if (pColumn.a[j].idx == i) break;
                        }
                    }
                    if (j < 0 || nColumn == 0 || (pColumn != null && j >= pColumn.nId))
                    {
                        ExprHelper.ExprCode(pParse, pTab.aCol[i].pDflt, iRegStore);
                    }
                    else if (useTempTable)
                    {
                        VdbeAux.VdbeAddOp3(v, OPCode.OP_Column, srcTab, j, iRegStore);
                    }
                    else if (pSelect != null)
                    {
                        VdbeAux.VdbeAddOp2(v, OPCode.OP_SCopy, regFromSelect + j, iRegStore);
                    }
                    else
                    {
                        ExprHelper.ExprCode(pParse, pList.a[j].pExpr, iRegStore);
                    }
                }

                /* Generate code to check constraints and generate index keys and
        ** do the insertion.
        */
#if !SQLITE_OMIT_VIRTUALTABLE
    if( Utility.IsVirtual(pTab) ){
      const char *pVTab = (const char *)sqlite3GetVTable(db, pTab);
      sqlite3VtabMakeWritable(pParse, pTab);
      VdbeAux.VdbeAddOp4(v, OPCode.OP_VUpdate, 1, pTab.nCol+2, regIns, pVTab, P4Type.P4_VTAB);
      Build.MayAbort(pParse);
    }else
#endif
                {
                    int isReplace = 0; /* Set to true if constraints may cause a replace */
                    sqlite3GenerateConstraintChecks(pParse, pTab, baseCur, regIns, aRegIdx,
                                                    keyColumn >= 0 ? 1 : 0, false, onError, endOfLoop, ref isReplace
                        );
                    sqlite3FkCheck(pParse, pTab, 0, regIns);
                    sqlite3CompleteInsertion(
                        pParse, pTab, baseCur, regIns, aRegIdx, false, appendFlag, isReplace == 0
                        );
                }
            }

            /* Update the count of rows that are inserted
      */
            if ((db.flags & Flag.SQLITE_CountRows) != 0)
            {
                VdbeAux.VdbeAddOp2(v, OPCode.OP_AddImm, regRowCount, 1);
            }

#if !SQLITE_OMIT_TRIGGER
            if (pTrigger != null)
            {
                /* Code AFTER triggers */
                sqlite3CodeRowTrigger(pParse, pTrigger, TokenKeyword.TK_INSERT, null, TRIGGER_AFTER,
                                      pTab, regData - 2 - pTab.nCol, onError, endOfLoop);
            }
#endif

            /* The bottom of the main insertion loop, if the data source
** is a SELECT statement.
*/
            VdbeAux.VdbeResolveLabel(v, endOfLoop);
            if (useTempTable)
            {
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Next, srcTab, addrCont);
                VdbeAux.VdbeJumpHere(v, addrInsTop);
                VdbeAux.VdbeAddOp1(v, OPCode.OP_Close, srcTab);
            }
            else if (pSelect != null)
            {
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Goto, 0, addrCont);
                VdbeAux.VdbeJumpHere(v, addrInsTop);
            }

            if (!Utility.IsVirtual(pTab) && !isView)
            {
                /* Close all tables opened */
                VdbeAux.VdbeAddOp1(v, OPCode.OP_Close, baseCur);
                for (idx = 1, pIdx = pTab.pIndex; pIdx != null; pIdx = pIdx.pNext, idx++)
                {
                    VdbeAux.VdbeAddOp1(v, OPCode.OP_Close, idx + baseCur);
                }
            }

            insert_end:
            /* Update the sqlite_sequence table by storing the content of the
      ** maximum rowid counter values recorded while inserting into
      ** autoincrement tables.
      */
            if (pParse.nested == 0 && pParse.pTriggerTab == null)
            {
                sqlite3AutoincrementEnd(pParse);
            }

            /*
      ** Return the number of rows inserted. If this routine is
      ** generating code because of a call to Build.NestedParse(), do not
      ** invoke the callback function.
      */
            if ((db.flags & Flag.SQLITE_CountRows) != 0 && 0 == pParse.nested && null == pParse.pTriggerTab)
            {
                VdbeAux.VdbeAddOp2(v, OPCode.OP_ResultRow, regRowCount, 1);
                VdbeAux.VdbeSetNumCols(v, 1);
                VdbeAux.VdbeSetColName(v, 0, VdbeColnameType.COLNAME_NAME, "rows inserted", Const.SQLITE_STATIC);
            }

            insert_cleanup:
            Build.SrcListDelete(db, ref pTabList);
            ExprHelper.ExprListDelete(db, ref pList);
            sqlite3SelectDelete(db, ref pSelect);
            Build.IdListDelete(db, ref pColumn);
            MemPool.DbFree(db, ref aRegIdx);
        }

        /* Make sure "isView" and other macros defined above are undefined. Otherwise
    ** thely may interfere with compilation of other functions in this file
    ** (or in another file, if this file becomes part of the amalgamation).  */
        //#ifdef isView
        // #undef isView
        //#endif
        //#ifdef pTrigger
        // #undef pTrigger
        //#endif
        //#ifdef tmask
        // #undef tmask
        //#endif

        /*
    ** Generate code to do constraint checks prior to an INSERT or an UPDATE.
    **
    ** The input is a range of consecutive registers as follows:
    **
    **    1.  The rowid of the row after the update.
    **
    **    2.  The data in the first column of the entry after the update.
    **
    **    i.  Data from middle columns...
    **
    **    N.  The data in the last column of the entry after the update.
    **
    ** The regRowid parameter is the index of the register containing (1).
    **
    ** If isUpdate is true and rowidChng is non-zero, then rowidChng contains
    ** the address of a register containing the rowid before the update takes
    ** place. isUpdate is true for UPDATEs and false for INSERTs. If isUpdate
    ** is false, indicating an INSERT statement, then a non-zero rowidChng 
    ** indicates that the rowid was explicitly specified as part of the
    ** INSERT statement. If rowidChng is false, it means that  the rowid is
    ** computed automatically in an insert or that the rowid value is not 
    ** modified by an update.
    **
    ** The code generated by this routine store new index entries into
    ** registers identified by aRegIdx[].  No index entry is created for
    ** indices where aRegIdx[i]==0.  The order of indices in aRegIdx[] is
    ** the same as the order of indices on the linked list of indices
    ** attached to the table.
    **
    ** This routine also generates code to check constraints.  NOT NULL,
    ** CHECK, and UNIQUE constraints are all checked.  If a constraint fails,
    ** then the appropriate action is performed.  There are five possible
    ** actions: ROLLBACK, ABORT, FAIL, REPLACE, and IGNORE.
    **
    **  Constraint type  Action       What Happens
    **  ---------------  ----------   ----------------------------------------
    **  any              ROLLBACK     The current transaction is rolled back and
    **                                sqlite3_exec() returns immediately with a
    **                                return code of StatusCode.SQLITE_CONSTRAINT.
    **
    **  any              ABORT        Back out changes from the current command
    **                                only (do not do a complete rollback) then
    **                                cause sqlite3_exec() to return immediately
    **                                with StatusCode.SQLITE_CONSTRAINT.
    **
    **  any              FAIL         Sqlite_exec() returns immediately with a
    **                                return code of StatusCode.SQLITE_CONSTRAINT.  The
    **                                transaction is not rolled back and any
    **                                prior changes are retained.
    **
    **  any              IGNORE       The record number and data is popped from
    **                                the stack and there is an immediate jump
    **                                to label ignoreDest.
    **
    **  NOT NULL         REPLACE      The NULL value is replace by the default
    **                                value for that column.  If the default value
    **                                is NULL, the action is the same as ABORT.
    **
    **  UNIQUE           REPLACE      The other row that conflicts with the row
    **                                being inserted is removed.
    **
    **  CHECK            REPLACE      Illegal.  The results in an exception.
    **
    ** Which action to take is determined by the overrideError parameter.
    ** Or if overrideError==OnConstraintError.OE_Default, then the pParse.onError parameter
    ** is used.  Or if pParse.onError==OnConstraintError.OE_Default then the onError value
    ** for the constraint is used.
    **
    ** The calling routine must open a read/write cursor for pTab with
    ** cursor number "baseCur".  All indices of pTab must also have open
    ** read/write cursors with cursor number baseCur+i for the i-th cursor.
    ** Except, if there is no possibility of a REPLACE action then
    ** cursors do not need to be open for indices where aRegIdx[i]==0.
    */

        private static void sqlite3GenerateConstraintChecks(
            Parse pParse, /* The parser context */
            Table pTab, /* the table into which we are inserting */
            int baseCur, /* Index of a read/write cursor pointing at pTab */
            int regRowid, /* Index of the range of input registers */
            int[] aRegIdx, /* Register used by each index.  0 for unused indices */
            int rowidChng, /* True if the rowid might collide with existing entry */
            bool isUpdate, /* True for UPDATE, False for INSERT */
            int overrideError, /* Override onError to this if not OnConstraintError.OE_Default */
            int ignoreDest, /* Jump to this label on an OnConstraintError.OE_Ignore resolution */
            ref int pbMayReplace /* OUT: Set to true if constraint may cause a replace */
            )
        {
            int i; /* loop counter */
            Vdbe v; /* VDBE under constrution */
            int nCol; /* Number of columns */
            int onError; /* Conflict resolution strategy */
            int j1; /* Addresss of jump instruction */
            int j2 = 0, j3; /* Addresses of jump instructions */
            int regData; /* Register containing first data column */
            int iCur; /* Table cursor number */
            Index pIdx; /* Pointer to one of the indices */
            bool seenReplace = false; /* True if REPLACE is used to resolve INT PK conflict */
            int regOldRowid = (rowidChng != 0 && isUpdate) ? rowidChng : regRowid;

            v = SelectHelper.GetVdbe(pParse);
            Debug.Assert(v != null);
            Debug.Assert(pTab.pSelect == null); /* This table is not a VIEW */
            nCol = pTab.nCol;
            regData = regRowid + 1;


            /* Test all NOT NULL constraints.
      */
            for (i = 0; i < nCol; i++)
            {
                if (i == pTab.iPKey)
                {
                    continue;
                }
                onError = pTab.aCol[i].notNull;
                if (onError == OnConstraintError.OE_None) continue;
                if (overrideError != OnConstraintError.OE_Default)
                {
                    onError = overrideError;
                }
                else if (onError == OnConstraintError.OE_Default)
                {
                    onError = OnConstraintError.OE_Abort;
                }
                if (onError == OnConstraintError.OE_Replace && pTab.aCol[i].pDflt == null)
                {
                    onError = OnConstraintError.OE_Abort;
                }
                Debug.Assert(onError == OnConstraintError.OE_Rollback || onError == OnConstraintError.OE_Abort || onError == OnConstraintError.OE_Fail
                             || onError == OnConstraintError.OE_Ignore || onError == OnConstraintError.OE_Replace);
                switch (onError)
                {
                    case OnConstraintError.OE_Abort:
                        {
                            Build.MayAbort(pParse);
                            goto case OnConstraintError.OE_Fail;
                        }
                    case OnConstraintError.OE_Rollback:
                    case OnConstraintError.OE_Fail:
                        {
                            string zMsg;
                            j1 = VdbeAux.VdbeAddOp3(v, OPCode.OP_HaltIfNull,
                                                   StatusCode.SQLITE_CONSTRAINT, onError, regData + i);
                            zMsg = Print.MPrintf(pParse.db, "%s.%s may not be NULL",
                                                  pTab.zName, pTab.aCol[i].zName);
                            VdbeAux.VdbeChangeP4(v, -1, zMsg, P4Type.P4_DYNAMIC);
                            break;
                        }
                    case OnConstraintError.OE_Ignore:
                        {
                            VdbeAux.VdbeAddOp2(v, OPCode.OP_IsNull, regData + i, ignoreDest);
                            break;
                        }
                    default:
                        {
                            Debug.Assert(onError == OnConstraintError.OE_Replace);
                            j1 = VdbeAux.VdbeAddOp1(v, OPCode.OP_NotNull, regData + i);
                            ExprHelper.ExprCode(pParse, pTab.aCol[i].pDflt, regData + i);
                            VdbeAux.VdbeJumpHere(v, j1);
                            break;
                        }
                }
            }

            /* Test all CHECK constraints
      */
#if !SQLITE_OMIT_CHECK
            if (pTab.pCheck != null && (pParse.db.flags & Flag.SQLITE_IgnoreChecks) == 0)
            {
                int allOk = VdbeAux.VdbeMakeLabel(v);
                pParse.ckBase = regData;
                ExprHelper.ExprIfTrue(pParse, pTab.pCheck, allOk, Const.SQLITE_JUMPIFNULL);
                onError = overrideError != OnConstraintError.OE_Default ? overrideError : OnConstraintError.OE_Abort;
                if (onError == OnConstraintError.OE_Ignore)
                {
                    VdbeAux.VdbeAddOp2(v, OPCode.OP_Goto, 0, ignoreDest);
                }
                else
                {
                    Build.HaltConstraint(pParse, onError, (string) null, 0);
                }
                VdbeAux.VdbeResolveLabel(v, allOk);
            }
#endif
            // * !SQLITE_OMIT_CHECK) */

            /* If we have an INTEGER PRIMARY KEY, make sure the primary key
** of the new record does not previously exist.  Except, if this
** is an UPDATE and the primary key is not changing, that is OK.
*/
            if (rowidChng != 0)
            {
                onError = pTab.keyConf;
                if (overrideError != OnConstraintError.OE_Default)
                {
                    onError = overrideError;
                }
                else if (onError == OnConstraintError.OE_Default)
                {
                    onError = OnConstraintError.OE_Abort;
                }

                if (isUpdate)
                {
                    j2 = VdbeAux.VdbeAddOp3(v, OPCode.OP_Eq, regRowid, 0, rowidChng);
                }
                j3 = VdbeAux.VdbeAddOp3(v, OPCode.OP_NotExists, baseCur, 0, regRowid);
                switch (onError)
                {
                    default:
                        {
                            onError = OnConstraintError.OE_Abort;
                            /* Fall thru into the next case */
                        }
                        goto case OnConstraintError.OE_Rollback;
                    case OnConstraintError.OE_Rollback:
                    case OnConstraintError.OE_Abort:
                    case OnConstraintError.OE_Fail:
                        {
                            Build.HaltConstraint(
                                pParse, onError, "PRIMARY KEY must be unique", P4Type.P4_STATIC);
                            break;
                        }
                    case OnConstraintError.OE_Replace:
                        {
                            /* If there are DELETE triggers on this table and the
              ** recursive-triggers flag is set, call GenerateRowDelete() to
              ** remove the conflicting row from the the table. This will fire
              ** the triggers and remove both the table and index b-tree entries.
              **
              ** Otherwise, if there are no triggers or the recursive-triggers
              ** flag is not set, but the table has one or more indexes, call 
              ** GenerateRowIndexDelete(). This removes the index b-tree entries 
              ** only. The table b-tree entry will be replaced by the new entry 
              ** when it is inserted.  
              **
              ** If either GenerateRowDelete() or GenerateRowIndexDelete() is called,
              ** also invoke MultiWrite() to indicate that this VDBE may require
              ** statement rollback (if the statement is aborted after the delete
              ** takes place). Earlier versions called Build.MultiWrite() regardless,
              ** but being more selective here allows statements like:
              **
              **   REPLACE INTO t(rowid) VALUES($newrowid)
              **
              ** to run without a statement journal if there are no indexes on the
              ** table.
              */
                            Trigger pTrigger = null;
                            if ((pParse.db.flags & Flag.SQLITE_RecTriggers) != 0)
                            {
                                int iDummy = 0;
                                pTrigger = sqlite3TriggersExist(pParse, pTab, TokenKeyword.TK_DELETE, null, ref iDummy);
                            }
                            if (pTrigger != null || sqlite3FkRequired(pParse, pTab, null, 0) != 0)
                            {
                                Build.MultiWrite(pParse);
                                sqlite3GenerateRowDelete(
                                    pParse, pTab, baseCur, regRowid, 0, pTrigger, OnConstraintError.OE_Replace
                                    );
                            }
                            else if (pTab.pIndex != null)
                            {
                                Build.MultiWrite(pParse);
                                sqlite3GenerateRowIndexDelete(pParse, pTab, baseCur, 0);
                            }
                            seenReplace = true;
                            break;
                        }
                    case OnConstraintError.OE_Ignore:
                        {
                            Debug.Assert(!seenReplace);
                            VdbeAux.VdbeAddOp2(v, OPCode.OP_Goto, 0, ignoreDest);
                            break;
                        }
                }
                VdbeAux.VdbeJumpHere(v, j3);
                if (isUpdate)
                {
                    VdbeAux.VdbeJumpHere(v, j2);
                }
            }

            /* Test all UNIQUE constraints by creating entries for each UNIQUE
      ** index and making sure that duplicate entries do not already exist.
      ** Add the new records to the indices as we go.
      */
            for (iCur = 0, pIdx = pTab.pIndex; pIdx != null; pIdx = pIdx.pNext, iCur++)
            {
                int regIdx;
                int regR;

                if (aRegIdx[iCur] == 0) continue; /* Skip unused indices */

                /* Create a key for accessing the index entry */
                regIdx = ExprHelper.GetTempRange(pParse, pIdx.nColumn + 1);
                for (i = 0; i < pIdx.nColumn; i++)
                {
                    int idx = pIdx.aiColumn[i];
                    if (idx == pTab.iPKey)
                    {
                        VdbeAux.VdbeAddOp2(v, OPCode.OP_SCopy, regRowid, regIdx + i);
                    }
                    else
                    {
                        VdbeAux.VdbeAddOp2(v, OPCode.OP_SCopy, regData + idx, regIdx + i);
                    }
                }
                VdbeAux.VdbeAddOp2(v, OPCode.OP_SCopy, regRowid, regIdx + i);
                VdbeAux.VdbeAddOp3(v, OPCode.OP_MakeRecord, regIdx, pIdx.nColumn + 1, aRegIdx[iCur]);
                VdbeAux.VdbeChangeP4(v, -1, sqlite3IndexAffinityStr(v, pIdx), 0);
                ExprHelper.ExprCacheAffinityChange(pParse, regIdx, pIdx.nColumn + 1);

                /* Find out what action to take in case there is an indexing conflict */
                onError = pIdx.onError;
                if (onError == OnConstraintError.OE_None)
                {
                    ExprHelper.ReleaseTempRange(pParse, regIdx, pIdx.nColumn + 1);
                    continue; /* pIdx is not a UNIQUE index */
                }

                if (overrideError != OnConstraintError.OE_Default)
                {
                    onError = overrideError;
                }
                else if (onError == OnConstraintError.OE_Default)
                {
                    onError = OnConstraintError.OE_Abort;
                }
                if (seenReplace)
                {
                    if (onError == OnConstraintError.OE_Ignore) onError = OnConstraintError.OE_Replace;
                    else if (onError == OnConstraintError.OE_Fail) onError = OnConstraintError.OE_Abort;
                }


                /* Check to see if the new index entry will be unique */
                regR = ExprHelper.GetTempReg(pParse);
                VdbeAux.VdbeAddOp2(v, OPCode.OP_SCopy, regOldRowid, regR);
                j3 = VdbeAux.VdbeAddOp4(v, OPCode.OP_IsUnique, baseCur + iCur + 1, 0,
                                       regR, regIdx, //regR, SQLITE_INT_TO_PTR(regIdx),
                                       P4Type.P4_INT32);
                ExprHelper.ReleaseTempRange(pParse, regIdx, pIdx.nColumn + 1);

                /* Generate code that executes if the new index entry is not unique */
                Debug.Assert(onError == OnConstraintError.OE_Rollback || onError == OnConstraintError.OE_Abort || onError == OnConstraintError.OE_Fail
                             || onError == OnConstraintError.OE_Ignore || onError == OnConstraintError.OE_Replace);
                switch (onError)
                {
                    case OnConstraintError.OE_Rollback:
                    case OnConstraintError.OE_Abort:
                    case OnConstraintError.OE_Fail:
                        {
                            int j;
                            var errMsg = new StrAccum(200);
                            string zSep;
                            string zErr;

                            Print.StrAccumInit(errMsg, null, 0, 200);
                            errMsg.db = pParse.db;
                            zSep = pIdx.nColumn > 1 ? "columns " : "column ";
                            for (j = 0; j < pIdx.nColumn; j++)
                            {
                                string zCol = pTab.aCol[pIdx.aiColumn[j]].zName;
                                Print.StrAccumAppend(errMsg, zSep, -1);
                                zSep = ", ";
                                Print.StrAccumAppend(errMsg, zCol, -1);
                            }
                            Print.StrAccumAppend(errMsg,
                                                  pIdx.nColumn > 1 ? " are not unique" : " is not unique", -1);
                            zErr = Print.StrAccumFinish(errMsg);
                            Build.HaltConstraint(pParse, onError, zErr, 0);
                            MemPool.DbFree(errMsg.db, ref zErr);
                            break;
                        }
                    case OnConstraintError.OE_Ignore:
                        {
                            Debug.Assert(!seenReplace);
                            VdbeAux.VdbeAddOp2(v, OPCode.OP_Goto, 0, ignoreDest);
                            break;
                        }
                    default:
                        {
                            Trigger pTrigger = null;
                            Debug.Assert(onError == OnConstraintError.OE_Replace);
                            Build.MultiWrite(pParse);
                            if ((pParse.db.flags & Flag.SQLITE_RecTriggers) != 0)
                            {
                                int iDummy = 0;
                                pTrigger = sqlite3TriggersExist(pParse, pTab, TokenKeyword.TK_DELETE, null, ref iDummy);
                            }
                            sqlite3GenerateRowDelete(
                                pParse, pTab, baseCur, regR, 0, pTrigger, OnConstraintError.OE_Replace
                                );
                            seenReplace = true;
                            break;
                        }
                }
                VdbeAux.VdbeJumpHere(v, j3);
                ExprHelper.ReleaseTempReg(pParse, regR);
            }
            //if ( pbMayReplace )
            {
                pbMayReplace = seenReplace ? 1 : 0;
            }
        }

        /*
    ** This routine generates code to finish the INSERT or UPDATE operation
    ** that was started by a prior call to sqlite3GenerateConstraintChecks.
    ** A consecutive range of registers starting at regRowid contains the
    ** rowid and the content to be inserted.
    **
    ** The arguments to this routine should be the same as the first six
    ** arguments to sqlite3GenerateConstraintChecks.
    */

        private static void sqlite3CompleteInsertion(
            Parse pParse, /* The parser context */
            Table pTab, /* the table into which we are inserting */
            int baseCur, /* Index of a read/write cursor pointing at pTab */
            int regRowid, /* Range of content */
            int[] aRegIdx, /* Register used by each index.  0 for unused indices */
            bool isUpdate, /* True for UPDATE, False for INSERT */
            bool appendBias, /* True if this is Utility.Likely to be an append */
            bool useSeekResult /* True to set the USESEEKRESULT flag on OPCode.OPCode.OP_[Idx]Insert */
            )
        {
            int i;
            Vdbe v;
            int nIdx;
            Index pIdx;
            byte pik_flags;
            int regData;
            int regRec;

            v = SelectHelper.GetVdbe(pParse);
            Debug.Assert(v != null);
            Debug.Assert(pTab.pSelect == null); /* This table is not a VIEW */
            for (nIdx = 0, pIdx = pTab.pIndex; pIdx != null; pIdx = pIdx.pNext, nIdx++)
            {
            }
            for (i = nIdx - 1; i >= 0; i--)
            {
                if (aRegIdx[i] == 0) continue;
                VdbeAux.VdbeAddOp2(v, OPCode.OP_IdxInsert, baseCur + i + 1, aRegIdx[i]);
                if (useSeekResult)
                {
                    VdbeAux.VdbeChangeP5(v, P5Value.OPFLAG_USESEEKRESULT);
                }
            }
            regData = regRowid + 1;
            regRec = ExprHelper.GetTempReg(pParse);
            VdbeAux.VdbeAddOp3(v, OPCode.OP_MakeRecord, regData, pTab.nCol, regRec);
            sqlite3TableAffinityStr(v, pTab);
            ExprHelper.ExprCacheAffinityChange(pParse, regData, pTab.nCol);
            if (pParse.nested != 0)
            {
                pik_flags = 0;
            }
            else
            {
                pik_flags = P5Value.OPFLAG_NCHANGE;
                pik_flags |= (isUpdate ? P5Value.OPFLAG_ISUPDATE : P5Value.OPFLAG_LASTROWID);
            }
            if (appendBias)
            {
                pik_flags |= P5Value.OPFLAG_APPEND;
            }
            if (useSeekResult)
            {
                pik_flags |= P5Value.OPFLAG_USESEEKRESULT;
            }
            VdbeAux.VdbeAddOp3(v, OPCode.OP_Insert, baseCur, regRec, regRowid);
            if (pParse.nested == 0)
            {
                VdbeAux.VdbeChangeP4(v, -1, pTab.zName, P4Type.P4_STATIC);
            }
            VdbeAux.VdbeChangeP5(v, pik_flags);
        }

        /*
    ** Generate code that will open cursors for a table and for all
    ** indices of that table.  The "baseCur" parameter is the cursor number used
    ** for the table.  Indices are opened on subsequent cursors.
    **
    ** Return the number of indices on the table.
    */

        private static int sqlite3OpenTableAndIndices(
            Parse pParse, /* Parsing context */
            Table pTab, /* Table to be opened */
            int baseCur, /* VdbeCursor number assigned to the table */
            int op /* OPCode.OP_OpenRead or OPCode.OP_OpenWrite */
            )
        {
            int i;
            int iDb;
            Index pIdx;
            Vdbe v;

            if (Utility.IsVirtual(pTab)) return 0;
            iDb = sqlite3SchemaToIndex(pParse.db, pTab.pSchema);
            v = SelectHelper.GetVdbe(pParse);
            Debug.Assert(v != null);
            sqlite3OpenTable(pParse, baseCur, iDb, pTab, op);
            for (i = 1, pIdx = pTab.pIndex; pIdx != null; pIdx = pIdx.pNext, i++)
            {
                KeyInfo pKey = Build.IndexKeyinfo(pParse, pIdx);
                Debug.Assert(pIdx.pSchema == pTab.pSchema);
                VdbeAux.VdbeAddOp4(v, op, i + baseCur, pIdx.tnum, iDb,
                                  pKey, P4Type.P4_KEYINFO_HANDOFF);
#if SQLITE_DEBUG
                VdbeComment(v, "%s", pIdx.zName);
#endif
            }
            if (pParse.nTab < baseCur + i)
            {
                pParse.nTab = baseCur + i;
            }
            return i - 1;
        }


#if  SQLITE_TEST
    /*
** The following global variable is incremented whenever the
** transfer optimization is used.  This is used for testing
** purposes only - to make sure the transfer optimization really
** is happening when it is suppose to.
*/
    //static int sqlite3_xferopt_count = 0;
#endif
        // * SQLITE_TEST */


#if !SQLITE_OMIT_XFER_OPT
        /*
** Check to collation names to see if they are compatible.
*/

        private static bool xferCompatibleCollation(string z1, string z2)
        {
            if (z1 == null)
            {
                return z2 == null;
            }
            if (z2 == null)
            {
                return false;
            }
            return Utility.Sqlite3StrICmp(z1, z2) == 0;
        }


        /*
    ** Check to see if index pSrc is compatible as a source of data
    ** for index pDest in an insert transfer optimization.  The rules
    ** for a compatible index:
    **
    **    *   The index is over the same set of columns
    **    *   The same DESC and ASC markings occurs on all columns
    **    *   The same onError processing (OnConstraintError.OE_Abort, OnConstraintError.OE_Ignore, etc)
    **    *   The same collating sequence on each column
    */

        private static bool xferCompatibleIndex(Index pDest, Index pSrc)
        {
            int i;
            Debug.Assert(pDest != null && pSrc != null);
            Debug.Assert(pDest.pTable != pSrc.pTable);
            if (pDest.nColumn != pSrc.nColumn)
            {
                return false; /* Different number of columns */
            }
            if (pDest.onError != pSrc.onError)
            {
                return false; /* Different conflict resolution strategies */
            }
            for (i = 0; i < pSrc.nColumn; i++)
            {
                if (pSrc.aiColumn[i] != pDest.aiColumn[i])
                {
                    return false; /* Different columns indexed */
                }
                if (pSrc.aSortOrder[i] != pDest.aSortOrder[i])
                {
                    return false; /* Different sort orders */
                }
                if (!xferCompatibleCollation(pSrc.azColl[i], pDest.azColl[i]))
                {
                    return false; /* Different collating sequences */
                }
            }

            /* If no test above fails then the indices must be compatible */
            return true;
        }

        /*
    ** Attempt the transfer optimization on INSERTs of the form
    **
    **     INSERT INTO tab1 SELECT * FROM tab2;
    **
    ** This optimization is only attempted if
    **
    **    (1)  tab1 and tab2 have identical schemas including all the
    **         same indices and constraints
    **
    **    (2)  tab1 and tab2 are different tables
    **
    **    (3)  There must be no triggers on tab1
    **
    **    (4)  The result set of the SELECT statement is "*"
    **
    **    (5)  The SELECT statement has no WHERE, HAVING, ORDER BY, GROUP BY,
    **         or LIMIT clause.
    **
    **    (6)  The SELECT statement is a simple (not a compound) select that
    **         contains only tab2 in its FROM clause
    **
    ** This method for implementing the INSERT transfers raw records from
    ** tab2 over to tab1.  The columns are not decoded.  Raw records from
    ** the indices of tab2 are transfered to tab1 as well.  In so doing,
    ** the resulting tab1 has much less fragmentation.
    **
    ** This routine returns TRUE if the optimization is attempted.  If any
    ** of the conditions above fail so that the optimization should not
    ** be attempted, then this routine returns FALSE.
    */

        private static int xferOptimization(
            Parse pParse, /* Parser context */
            Table pDest, /* The table we are inserting into */
            Select pSelect, /* A SELECT statement to use as the data source */
            int onError, /* How to handle constraint errors */
            int iDbDest /* The database of pDest */
            )
        {
            ExprList pEList; /* The result set of the SELECT */
            Table pSrc; /* The table in the FROM clause of SELECT */
            Index pSrcIdx, pDestIdx; /* Source and destination indices */
            SrcList_item pItem; /* An element of pSelect.pSrc */
            int i; /* Loop counter */
            int iDbSrc; /* The database of pSrc */
            int iSrc, iDest; /* Cursors from source and destination */
            int addr1, addr2; /* Loop addresses */
            int emptyDestTest; /* Address of test for empty pDest */
            int emptySrcTest; /* Address of test for empty pSrc */
            Vdbe v; /* The VDBE we are building */
            KeyInfo pKey; /* Key information for an index */
            int regAutoinc; /* Memory register used by AUTOINC */
            bool destHasUniqueIdx = false; /* True if pDest has a UNIQUE index */
            int regData, regRowid; /* Registers holding data and rowid */

            if (pSelect == null)
            {
                return 0; /* Must be of the form  INSERT INTO ... SELECT ... */
            }
#if !SQLITE_OMIT_TRIGGER
            if (sqlite3TriggerList(pParse, pDest) != null)
            {
                return 0; /* tab1 must not have triggers */
            }
#endif

            if ((pDest.tabFlags & TabFlag.TF_Virtual) != 0)
            {
                return 0; /* tab1 must not be a virtual table */
            }
            if (onError == OnConstraintError.OE_Default)
            {
                onError = OnConstraintError.OE_Abort;
            }
            if (onError != OnConstraintError.OE_Abort && onError != OnConstraintError.OE_Rollback)
            {
                return 0; /* Cannot do OR REPLACE or OR IGNORE or OR FAIL */
            }
            Debug.Assert(pSelect.pSrc != null); /* allocated even if there is no FROM clause */
            if (pSelect.pSrc.nSrc != 1)
            {
                return 0; /* FROM clause must have exactly one term */
            }
            if (pSelect.pSrc.a[0].pSelect != null)
            {
                return 0; /* FROM clause cannot contain a subquery */
            }
            if (pSelect.pWhere != null)
            {
                return 0; /* SELECT may not have a WHERE clause */
            }
            if (pSelect.pOrderBy != null)
            {
                return 0; /* SELECT may not have an ORDER BY clause */
            }
            /* Do not need to test for a HAVING clause.  If HAVING is present but
      ** there is no ORDER BY, we will get an error. */
            if (pSelect.pGroupBy != null)
            {
                return 0; /* SELECT may not have a GROUP BY clause */
            }
            if (pSelect.pLimit != null)
            {
                return 0; /* SELECT may not have a LIMIT clause */
            }
            Debug.Assert(pSelect.pOffset == null); /* Must be so if pLimit==0 */
            if (pSelect.pPrior != null)
            {
                return 0; /* SELECT may not be a compound query */
            }
            if ((pSelect.selFlags & SelectFlag.SF_Distinct) != 0)
            {
                return 0; /* SELECT may not be DISTINCT */
            }
            pEList = pSelect.pEList;
            Debug.Assert(pEList != null);
            if (pEList.nExpr != 1)
            {
                return 0; /* The result set must have exactly one column */
            }
            Debug.Assert(pEList.a[0].pExpr != null);
            if (pEList.a[0].pExpr.op != TokenKeyword.TK_ALL)
            {
                return 0; /* The result set must be the special operator "*" */
            }

            /* At this point we have established that the statement is of the
      ** correct syntactic form to participate in this optimization.  Now
      ** we have to check the semantics.
      */
            pItem = pSelect.pSrc.a[0];
            pSrc = Build.LocateTable(pParse, 0, pItem.zName, pItem.zDatabase);
            if (pSrc == null)
            {
                return 0; /* FROM clause does not contain a real table */
            }
            if (pSrc == pDest)
            {
                return 0; /* tab1 and tab2 may not be the same table */
            }
            if ((pSrc.tabFlags & TabFlag.TF_Virtual) != 0)
            {
                return 0; /* tab2 must not be a virtual table */
            }
            if (pSrc.pSelect != null)
            {
                return 0; /* tab2 may not be a view */
            }
            if (pDest.nCol != pSrc.nCol)
            {
                return 0; /* Number of columns must be the same in tab1 and tab2 */
            }
            if (pDest.iPKey != pSrc.iPKey)
            {
                return 0; /* Both tables must have the same INTEGER PRIMARY KEY */
            }
            for (i = 0; i < pDest.nCol; i++)
            {
                if (pDest.aCol[i].affinity != pSrc.aCol[i].affinity)
                {
                    return 0; /* Affinity must be the same on all columns */
                }
                if (!xferCompatibleCollation(pDest.aCol[i].zColl, pSrc.aCol[i].zColl))
                {
                    return 0; /* Collating sequence must be the same on all columns */
                }
                if (pDest.aCol[i].notNull != 0 && pSrc.aCol[i].notNull == 0)
                {
                    return 0; /* tab2 must be NOT NULL if tab1 is */
                }
            }
            for (pDestIdx = pDest.pIndex; pDestIdx != null; pDestIdx = pDestIdx.pNext)
            {
                if (pDestIdx.onError != OnConstraintError.OE_None)
                {
                    destHasUniqueIdx = true;
                }
                for (pSrcIdx = pSrc.pIndex; pSrcIdx != null; pSrcIdx = pSrcIdx.pNext)
                {
                    if (xferCompatibleIndex(pDestIdx, pSrcIdx)) break;
                }
                if (pSrcIdx == null)
                {
                    return 0; /* pDestIdx has no corresponding index in pSrc */
                }
            }
#if !SQLITE_OMIT_CHECK
            if (pDest.pCheck != null && 0 != ExprHelper.ExprCompare(pSrc.pCheck, pDest.pCheck))
            {
                return 0; /* Tables have different CHECK constraints.  Ticket #2252 */
            }
#endif

            /* If we get this far, it means either:
      **
      **    *   We can always do the transfer if the table contains an
      **        an integer primary key
      **
      **    *   We can conditionally do the transfer if the destination
      **        table is empty.
      */
#if  SQLITE_TEST
      sqlite3_xferopt_count.iValue++;
#endif
            iDbSrc = sqlite3SchemaToIndex(pParse.db, pSrc.pSchema);
            v = SelectHelper.GetVdbe(pParse);
            Build.CodeVerifySchema(pParse, iDbSrc);
            iSrc = pParse.nTab++;
            iDest = pParse.nTab++;
            regAutoinc = autoIncBegin(pParse, iDbDest, pDest);
            sqlite3OpenTable(pParse, iDest, iDbDest, pDest, OPCode.OP_OpenWrite);
            if ((pDest.iPKey < 0 && pDest.pIndex != null) || destHasUniqueIdx)
            {
                /* If tables do not have an INTEGER PRIMARY KEY and there
        ** are indices to be copied and the destination is not empty,
        ** we have to disallow the transfer optimization because the
        ** the rowids might change which will mess up indexing.
        **
        ** Or if the destination has a UNIQUE index and is not empty,
        ** we also disallow the transfer optimization because we cannot
        ** insure that all entries in the union of DEST and SRC will be
        ** unique.
        */
                addr1 = VdbeAux.VdbeAddOp2(v, OPCode.OP_Rewind, iDest, 0);
                emptyDestTest = VdbeAux.VdbeAddOp2(v, OPCode.OP_Goto, 0, 0);
                VdbeAux.VdbeJumpHere(v, addr1);
            }
            else
            {
                emptyDestTest = 0;
            }
            sqlite3OpenTable(pParse, iSrc, iDbSrc, pSrc, OPCode.OP_OpenRead);
            emptySrcTest = VdbeAux.VdbeAddOp2(v, OPCode.OP_Rewind, iSrc, 0);
            regData = ExprHelper.GetTempReg(pParse);
            regRowid = ExprHelper.GetTempReg(pParse);
            if (pDest.iPKey >= 0)
            {
                addr1 = VdbeAux.VdbeAddOp2(v, OPCode.OP_Rowid, iSrc, regRowid);
                addr2 = VdbeAux.VdbeAddOp3(v, OPCode.OP_NotExists, iDest, 0, regRowid);
                Build.HaltConstraint(
                    pParse, onError, "PRIMARY KEY must be unique", P4Type.P4_STATIC);
                VdbeAux.VdbeJumpHere(v, addr2);
                autoIncStep(pParse, regAutoinc, regRowid);
            }
            else if (pDest.pIndex == null)
            {
                addr1 = VdbeAux.VdbeAddOp2(v, OPCode.OP_NewRowid, iDest, regRowid);
            }
            else
            {
                addr1 = VdbeAux.VdbeAddOp2(v, OPCode.OP_Rowid, iSrc, regRowid);
                Debug.Assert((pDest.tabFlags & TabFlag.TF_Autoincrement) == 0);
            }
            VdbeAux.VdbeAddOp2(v, OPCode.OP_RowData, iSrc, regData);
            VdbeAux.VdbeAddOp3(v, OPCode.OP_Insert, iDest, regData, regRowid);
            VdbeAux.VdbeChangeP5(v, P5Value.OPFLAG_NCHANGE | P5Value.OPFLAG_LASTROWID | P5Value.OPFLAG_APPEND);
            VdbeAux.VdbeChangeP4(v, -1, pDest.zName, 0);
            VdbeAux.VdbeAddOp2(v, OPCode.OP_Next, iSrc, addr1);
            for (pDestIdx = pDest.pIndex; pDestIdx != null; pDestIdx = pDestIdx.pNext)
            {
                for (pSrcIdx = pSrc.pIndex; pSrcIdx != null; pSrcIdx = pSrcIdx.pNext)
                {
                    if (xferCompatibleIndex(pDestIdx, pSrcIdx)) break;
                }
                Debug.Assert(pSrcIdx != null);
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Close, iSrc, 0);
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Close, iDest, 0);
                pKey = Build.IndexKeyinfo(pParse, pSrcIdx);
                VdbeAux.VdbeAddOp4(v, OPCode.OP_OpenRead, iSrc, pSrcIdx.tnum, iDbSrc,
                                  pKey, P4Type.P4_KEYINFO_HANDOFF);
#if SQLITE_DEBUG
                VdbeComment(v, "%s", pSrcIdx.zName);
#endif
                pKey = Build.IndexKeyinfo(pParse, pDestIdx);
                VdbeAux.VdbeAddOp4(v, OPCode.OP_OpenWrite, iDest, pDestIdx.tnum, iDbDest,
                                  pKey, P4Type.P4_KEYINFO_HANDOFF);
#if SQLITE_DEBUG
                VdbeComment(v, "%s", pDestIdx.zName);
#endif
                addr1 = VdbeAux.VdbeAddOp2(v, OPCode.OP_Rewind, iSrc, 0);
                VdbeAux.VdbeAddOp2(v, OPCode.OP_RowKey, iSrc, regData);
                VdbeAux.VdbeAddOp3(v, OPCode.OP_IdxInsert, iDest, regData, 1);
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Next, iSrc, addr1 + 1);
                VdbeAux.VdbeJumpHere(v, addr1);
            }
            VdbeAux.VdbeJumpHere(v, emptySrcTest);
            ExprHelper.ReleaseTempReg(pParse, regRowid);
            ExprHelper.ReleaseTempReg(pParse, regData);
            VdbeAux.VdbeAddOp2(v, OPCode.OP_Close, iSrc, 0);
            VdbeAux.VdbeAddOp2(v, OPCode.OP_Close, iDest, 0);
            if (emptyDestTest != 0)
            {
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Halt, StatusCode.SQLITE_OK, 0);
                VdbeAux.VdbeJumpHere(v, emptyDestTest);
                VdbeAux.VdbeAddOp2(v, OPCode.OP_Close, iDest, 0);
                return 0;
            }
            else
            {
                return 1;
            }
        }
#endif
        // * SQLITE_OMIT_XFER_OPT */
    }
}